package site.luojie.platform.gateway.filter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Ip 防爆刷过滤器
 *
 * @author jie.luo
 * @since 2020/9/21
 */
@Component
public class IpLimitFilter implements GlobalFilter, Ordered, Runnable {

    private final static Logger LOGGER = LoggerFactory.getLogger(IpLimitFilter.class);

    /**
     * 监控时间窗口(毫秒)
     */
    private final static int WINDOW_TIME = 60 * 1000;
    /**
     * 监控时间窗口请求次数
     */
    private final static int WINDOW_TIME_REQUEST_NUM = 10;


    private Map<String, List<LocalDateTime>> IP_REQUEST_DATE = new ConcurrentHashMap<>();

    public IpLimitFilter() {
        Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(this, 1, 5, TimeUnit.SECONDS);
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        LocalDateTime currentRequestDateTime = LocalDateTime.now();

        // 客户端ip
        String clientId = exchange.getRequest().getRemoteAddress().getHostString();

        List<LocalDateTime> requestDateTimes = IP_REQUEST_DATE.get(clientId);
        if (CollectionUtils.isEmpty(requestDateTimes)) {

            requestDateTimes = new ArrayList<>();
            requestDateTimes.add(currentRequestDateTime);
            IP_REQUEST_DATE.put(clientId, requestDateTimes);

            LOGGER.debug("当前IP{}第一次请求请求", clientId);
        } else {

            requestDateTimes.add(currentRequestDateTime);
            int requestNum = requestDateTimes.size();

            LOGGER.debug("当前IP{}时间窗口内请求次数：{}", clientId, requestNum);
            // 记录的首次请求时间
            if (requestNum > WINDOW_TIME_REQUEST_NUM) {
                String frequently = "{\"code\":\"501\",\"message\":\"操作太频繁了，等一哈再试\"}";
                return response(exchange, frequently);
            }

        }
        return chain.filter(exchange);
    }

    public Mono<Void> response(ServerWebExchange exchange, String responseContent) {
        ServerHttpResponse originalResponse = exchange.getResponse();
        originalResponse.setStatusCode(HttpStatus.OK);
        originalResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        byte[] response = responseContent.getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = originalResponse.bufferFactory().wrap(response);
        return originalResponse.writeWith(Flux.just(buffer));
    }

    @Override
    public int getOrder() {
        return 0;
    }

    @Override
    public void run() {

        final LocalDateTime current = LocalDateTime.now();

        for (Map.Entry<String, List<LocalDateTime>> entry : IP_REQUEST_DATE.entrySet()) {

            // 客户端ip
            String clientId = entry.getKey();
            // 当前ip请求集合
            List<LocalDateTime> dateTimes = entry.getValue();
            if (CollectionUtils.isEmpty(dateTimes)) {
                LOGGER.debug("当前IP{}在时间窗口内没有请求", clientId);
                continue;
            }

            // 过滤在时间窗口内的请求
            List<LocalDateTime> collect = dateTimes.stream().
                    filter(localDateTime -> Duration.between(localDateTime, current).toMillis() < WINDOW_TIME).
                    collect(Collectors.toList());

            // 重置请求信息
            IP_REQUEST_DATE.put(clientId, collect);
        }

    }
}
